#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#include "GThread.h"
#include "GError.h"

namespace GThread
{
	// Description: Converts GThreadPriority to windows priority types
	// Return:
	// Notes:
	// ----------------------------------------------------
	GUInt32 GetWin32ThreadPriority(GThreadPriority Priority)
	{
		static const GUInt32 WrapperArray[5] = 
		{
			THREAD_PRIORITY_HIGHEST,
			THREAD_PRIORITY_ABOVE_NORMAL,
			THREAD_PRIORITY_NORMAL,
			THREAD_PRIORITY_BELOW_NORMAL,
			THREAD_PRIORITY_LOWEST
		};

		return WrapperArray[(GUInt32)Priority];
	}

	// Description: Sets the priority of the thread.
	// Return: True if successfull, false otherwise
	// Notes: Thread must be created.
	// ---------------------------------------------------
	bool GThread::SetPriority(GThreadPriority NewPriority)
	{
		GUtil::GError::DebugCheck(!IsCreated(), "Thread is not created.");
		GUtil::GError::DebugCheck(GetState() == G_TS_RELEASED, "Thread is not active.");
		GUtil::GError::DebugCheck(GetState() == G_TS_TERMINATED, "Thread is not active.");

		BOOL Result = SetThreadPriority(Handle, GetWin32ThreadPriority(NewPriority ));
		if (Result == 0)
		{
			GUtil::GError::Error(GUtil::GErrorMessageWin32(GetLastError()));
			return false;
		}

		Priority.store(NewPriority, std::memory_order_release);
		return true;
	}

	// Description: Pauses a running thread
	// Return: True if successfull, false otherwise
	// Notes1: Pausing a thread will increate its referance count. Thread will not resume till referance count is zero
	// Notes2: Returns true in case of a referance count increase.
	// ----------------------------------------------------
	bool GThread::Pause()
	{
		GUtil::GError::DebugCheck(!IsCreated(), "Thread is not created.");
		GUtil::GError::DebugCheck(GetState() == G_TS_RELEASED, "Thread is not active.");
		GUtil::GError::DebugCheck(GetState() == G_TS_TERMINATED, "Thread is not active.");

		GThreadState OldState = GetState();
		State.store(G_TS_PAUSED, std::memory_order_release);

		DWORD Result = SuspendThread(Handle);
		if (Result == -1)
		{
			GUtil::GError::Error(GUtil::GErrorMessageWin32(GetLastError()));

			State.store(OldState, std::memory_order_release);
			return false;
		}

		return true;
	}

	// Description: Resumes a pasued thread
	// Return: True if successfull, false otherwise
	// Notes1: Resuming a thread does not mean it will start running, it will be pasued until its referance count drops to zero.
	// Notes2: Returns true in case of a referance count decrease.
	// Notes2: Returns true if referance count is already zero.
	// Notes3: State is set to Running if Resume() starts the thread.
	// ----------------------------------------------------
	bool GThread::Resume()
	{
		GUtil::GError::DebugCheck(!IsCreated(), "Thread is not created.");
		GUtil::GError::DebugCheck(GetState() == G_TS_RELEASED, "Thread is released.");
		GUtil::GError::DebugCheck(GetState() == G_TS_TERMINATED, "Thread is terminated.");

		if (GetState() != G_TS_PAUSED)
		{
			return true;
		}

		DWORD Result = ResumeThread(Handle);
		if (Result == -1)
		{
			GUtil::GError::Error(GUtil::GErrorMessageWin32(GetLastError()));
			return false;
		}
		else if (Result == 1)
		{
			State.store(G_TS_RUNNING, std::memory_order_release);
		}
	
		return true;
	}

	// Description: Lets user know if the thread did exit or not
	// Return: True if did exit, false otherwise
	// Notes1:
	bool GThread::DidExit(GUInt32& ExitCode)
	{
		DWORD Status = 0;
		BOOL Result = GetExitCodeThread(Handle, &Status);
		if (Result == 0)
		{
			GUtil::GError::Error(GUtil::GErrorMessageWin32(GetLastError()));
			return false;
		}
	
		ExitCode = Status;

		return ExitCode != STILL_ACTIVE;
	}

	// Description: Creates gthread from an existing o.s. thread
	// Return: True if successfull, false otherwise
	// Notes 1: Must be called by the thread whoose handle is given
	// Notes 2: Cannot create if it is already created.
	bool GThread::Create(void* Handle) PLATFORM
	{
		GUtil::GError::DebugCheck(GetState() != G_TS_RELEASED, "Thread is not released.");

		EntryMethod = NULL;
		Parameters = NULL;
		Id = GetThreadId(Handle);
		State.store(G_TS_RUNNING, std::memory_order_release);

		return true;
	}

	// Description: Creates the thread
	// Return: True if successfull, false otherwise
	// Notes 1: Parameters should not be a class. Only use structs.
	// Notes 2: Cannot create the thread if it is already created.
	bool GThread::Create(bool StartSuspended, GEntryMethod Entry, void* Params, GSize StackSize)
	{
		GUtil::GError::DebugCheck(GetState() != G_TS_RELEASED, "Thread is not released.");

		DWORD Flags = 0;
		Flags |= StartSuspended ? CREATE_SUSPENDED : 0;

		Handle = CreateThread(NULL, StackSize, &ThreadEntryMethod, (void*)this, Flags, (LPDWORD)&Id);
		if (Handle  == NULL)
		{
			GUtil::GError::Error(GUtil::GErrorMessageWin32(GetLastError()));
			return false;
		}

		EntryMethod = Entry;
		Parameters = Params;
		State.store(G_TS_PAUSED, std::memory_order_release);

		return true;
	}

	// Description: Releases the thread
	// Return:
	// Notes1: Resets all the states to default.
	// Notes2: Thread must be exited by itself before this method is called!
	void GThread::Release(bool WaitTillExit)
	{
		GUtil::GError::DebugCheck(GetCurrentThread()->GetId() == GetId(), "Thread cannot release itself.");

		if (!IsCreated())
		{
			return;
		}

		if (WaitTillExit)
		{
			GSynch::WaitFor(this);
		}

		CloseHandle(Handle);
		Handle = NULL;
		Parameters = NULL;
		
		State.store(G_TS_RELEASED, std::memory_order_release);
		Priority.store(G_TP_NORMAL, std::memory_order_release);		
		EntryMethod = NULL;
		Id = 0;
	}

	// Description: Sleeps the calling thread for specified time
	// Return: True if successfull, false otherwise
	// Notes:
	// ----------------------------------------------------
	bool GThread::Sleep(GUInt32 TimeOut)
	{
		GUtil::GError::DebugCheck(::mGCurrentThread == NULL, "Thread is not created as a GThread.");
		GUtil::GError::DebugCheck(::mGCurrentThread->GetState() != G_TS_RUNNING, "Only running threads can sleep.");

		::mGCurrentThread->State.store(G_TS_SLEEPING, std::memory_order_release);
		::Sleep(TimeOut);
		::mGCurrentThread->State.store(G_TS_RUNNING, std::memory_order_release);

		return true;
	}

	void GThread::SwitchThread()
	{
		::SwitchToThread();
	}

	GUInt32 GThread::GetCurrentThreadId()
	{
		return ::GetCurrentThreadId();
	}
}